/*
 * ClientAcceptor.cpp
 *
 *  Created on: 08.12.2011
 *      Author: Pavel Sinay
 */

#include "ClientAcceptor.h"
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdio>
#include <fcntl.h>
#include <algorithm>
#include <sys/poll.h>

#include "../NetException.h"
#include "../constants.h"

namespace net {

ClientAcceptor::ClientAcceptor():m_thread(0), m_stop_req(false), m_started(false), m_port(0)  {

}

ClientAcceptor::~ClientAcceptor() {
	stop();
}


void ClientAcceptor::start(unsigned short port) {
	if (!m_started) {
		startServer(port);

		m_stop_req = false;
		pthread_attr_init(&m_attr);
		pthread_attr_setdetachstate(&m_attr, PTHREAD_CREATE_JOINABLE);
		pthread_create(&m_thread, &m_attr, thread_operation, (void*) this);
		m_started = true;
		std::cerr << "ReceiverRaw::start(unsigned short port)" << std::endl;
	}
}

int ClientAcceptor::startServer(unsigned short port) {
	struct sockaddr_in addr;

	m_fd_server_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (m_fd_server_socket < 0) {
		throwException(
				"ClientAcceptor::startServer(unsigned short port) ERROR: socket");
	}

	// non-blocking mode
	fcntl(m_fd_server_socket, F_SETFL, O_NONBLOCK);

	// setting address and m_port
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;

	// this operation prevents "Address already in use" error
	int tr = 1;
	if (setsockopt(m_fd_server_socket, SOL_SOCKET, SO_REUSEADDR, &tr,
			sizeof(int)) == -1) {
		throwException("ClientAcceptor::startServer() ERROR: setsockopt");
	}

	if (bind(m_fd_server_socket, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
		//printError("bind");
		throwException("ClientAcceptor::startServer() ERROR: bind");
	}

	socklen_t l = sizeof(addr);
	getsockname(m_fd_server_socket, (struct sockaddr *) &addr, &l);
	m_port = ntohs(addr.sin_port);

	listen(m_fd_server_socket, 2);
}

void ClientAcceptor::stop() {
	if (m_started) {
		std::cerr << "ClientAcceptor::stop()" << std::endl;
		m_started = false;
		m_stop_req = true;
		pthread_join(m_thread, NULL);
		pthread_attr_destroy(&m_attr);
		stopServer();
	}
}

void ClientAcceptor::stopServer() {
	if (m_fd_server_socket){
		close(m_fd_server_socket);
		m_fd_server_socket = 0;
	}
}

void *ClientAcceptor::thread_operation(void *this_) {
	static_cast<ClientAcceptor*> (this_)->listening();
}

void ClientAcceptor::listening() {

	Socket *client_socket = NULL;
	while (1) {
		FD_ZERO (&m_readset);
		FD_SET (m_fd_server_socket, &m_readset);

		// set the timeout
		timeval timeout;
		timeout.tv_sec = 0;
		timeout.tv_usec = C_TIMEOUT * 1000;

		if (m_stop_req) {
			return;
		}

		// waiting for event in socket
		if (select(m_fd_server_socket + 1, &m_readset, NULL, NULL, &timeout)
				<= 0) {
			continue;
		}

		if (FD_ISSET(m_fd_server_socket, &m_readset)) {
			std::cerr << "ClientAcceptor::listening(): new client" << std::endl;
			int fd_client_socket;
			fd_client_socket = accept(m_fd_server_socket, NULL, NULL);

			delete client_socket;
			client_socket = new Socket(fd_client_socket);
			onClientAccepted(client_socket);
		}
	}
}

unsigned short ClientAcceptor::getPort() const {
	return m_port;
}

void ClientAcceptor::throwException(const std::string &msg){
	stop();
	throw NetException(msg);
}


}
